using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using Microsoft.WindowsAzure.Storage.Queue;
using Analyzer.Entities;
using Analyzer.Models;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage.Blob;
using System.IO;
using System.Reflection;
using OsmSharp.Routing.TSP.Genetic;
using OsmSharp.Routing.TSP;
using OsmSharp.Routing.Route;
using OsmSharp.Osm;
using OsmSharp.Routing.Osm.Interpreter;
using Microsoft.WindowsAzure.Diagnostics.Management;
using System.Configuration;

namespace Analyzer
{
    public class WorkerRole : RoleEntryPoint
    {
#error STORAGE_CONNECTION_STRING must be defined in app.config
        static private string STORAGE_CONNECTION_STRING = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

        private CloudBlobContainer reportsContainer;
        private CloudTable rawdataTable;
        private CloudQueue reportQueue;

        public override void Run()
        {
            CloudQueueMessage msg = null;

            // This is a sample worker implementation. Replace with your logic.
            Trace.WriteLine("Analyzer entry point called", "Information");

            try
            {
                Trace.WriteLine("Initializing OSM services...", "Information");
                RouterTSP tsp_solver = new RouterTSPAEXGenetic();
                RouteTags tags = new RouteTags();

                OsmTagsIndex tags_index = new OsmTagsIndex();
                OsmRoutingInterpreter interpreter = new OsmRoutingInterpreter();
                Trace.TraceInformation("OSM successfully initialized.");
            }
            catch (SystemException ex)
            {
                Trace.WriteLine("OSM initialization failed." + ex.Message, "Error");
            }


            while (true)
            {
                try
                {
                    bool messageFound = false;

                    Trace.WriteLine("Searching queue for pending operations...", "Information");

                    msg = reportQueue.GetMessage(); // Retrieve and process a new message from the report-queue
                    if (msg != null)
                    {
                        Trace.WriteLine("Message found, will attempt processing...", "Information");
                        this.ProcessReportQueueMessage(msg);
                        messageFound = true;
                    }

                    if (!messageFound)
                        Thread.Sleep(1000 * 60);
                }
                catch (SystemException e)
                {
                    Trace.TraceInformation("An error occurred while processing. " + e.Message, "Error");
                }
            }
        }

        private void ConfigureDiagnostics(CloudStorageAccount storageAccount)
        {
            string wadConnectionString = "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";

            LocalResource traceResource = RoleEnvironment.GetLocalResource("DiagnosticStore");
            DiagnosticMonitorConfiguration config = DiagnosticMonitor.GetDefaultInitialConfiguration();
            config.ConfigurationChangePollInterval = TimeSpan.FromMinutes(1d);
            config.Directories.DataSources.Add(new DirectoryConfiguration { Path = traceResource.RootPath, Container = "wad-trace-container", DirectoryQuotaInMB = 100 });
            config.Logs.BufferQuotaInMB = 500;
            config.Logs.ScheduledTransferLogLevelFilter = LogLevel.Verbose; // Transfer verbose, critical, etc. logs
            config.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1d); // Transfer logs to storage every minute

            DiagnosticMonitor.Start(wadConnectionString, config); // Start up the diagnostic manager with the given configuration
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount;

            // For information on handling configuration changes
            // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

            var storageAccount = CloudStorageAccount.Parse(STORAGE_CONNECTION_STRING);

            this.ConfigureDiagnostics(storageAccount);

            Trace.WriteLine("Initializing Storage Accounts for Analyzer");

            var blobClient = storageAccount.CreateCloudBlobClient();
            var tableClient = storageAccount.CreateCloudTableClient();
            var queueClient = storageAccount.CreateCloudQueueClient();

            rawdataTable = tableClient.GetTableReference("moments");
            rawdataTable.CreateIfNotExists();

            reportQueue = queueClient.GetQueueReference("vehiclereportqueue");
            reportQueue.CreateIfNotExists();

            reportsContainer = blobClient.GetContainerReference("reports");
            reportsContainer.CreateIfNotExists();

            return base.OnStart();
        }

        #region Private Methods

        private void ProcessReportQueueMessage(CloudQueueMessage msg)
        {
            if (msg.DequeueCount > 5)
            {
                Trace.WriteLine("Something is wrong with this report and it should be filed to debug, but for now we're deleting it", "Error");

                reportQueue.DeleteMessage(msg);
                return;
            }

            var messageParts = msg.AsString.Split(new char[] { ',' });
            var ReportGUID = messageParts[0];
            var VIN = messageParts[1];

            Trace.WriteLine("Initializing analyzing process for " + VIN + "(" + ReportGUID + ")", "Information");

            this.GenerateReport(ReportGUID, VIN);

            reportQueue.DeleteMessage(msg);
        }

        private void GenerateReport(string ReportGUID, string VIN)
        {
            // Fetch all data for this report
            TableQuery<MomentEntity> query = new TableQuery<MomentEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, ReportGUID));

            Trace.WriteLine("Created new query for pending report", "Information");

            ReportModel newReport = new ReportModel()
            {
                VIN = VIN
            };


            Trace.WriteLine("Executing query...", "Information");

            foreach (MomentEntity entity in rawdataTable.ExecuteQuery(query))
            {
                ReportModel.SnapshotModel snapshot = newReport.AddSnapshot();
                snapshot.Entry = entity.Entry;
                snapshot.Fuel = entity.Fuel;
                snapshot.Heading = entity.Heading;
                snapshot.Latitude = entity.Latitude;
                snapshot.Longitude = entity.Longitude;
                snapshot.Speed = entity.Speed;
                snapshot.Throttle = entity.Throttle;
                snapshot.VIN = VIN;
                snapshot.XAxis = entity.XAxis;
                snapshot.YAxis = entity.YAxis;
                snapshot.ZAxis = entity.ZAxis;
            }

            Trace.WriteLine("Query successfully executed against" + newReport.Snapshots.Count.ToString() + " rows.", "Information");
            Trace.WriteLine("Intializing report analysis...", "Information");

            newReport.Analyze();

            Trace.WriteLine("Analysis complete.", "Information");
            Trace.WriteLine("Converting report to blob...", "Information");

            this.SaveToJSON(newReport, ReportGUID);
        }

        /// <summary>
        /// Generate SVG XML string for Acceleration vs. Time graph and saves it model
        /// </summary>
        /// <param name="model"></param>
        private void GenerateAccelerationPlot(ReportModel model)
        {
            Thread t = new Thread(new ThreadStart(() =>
            {
                OxyPlot.PlotModel plot = new OxyPlot.PlotModel();
                plot.LegendBackground = OxyPlot.OxyColor.FromRgb(255, 255, 255);
                plot.Title = "Driver Acceleration Patterns";

                OxyPlot.Axes.LinearAxis linearAxis1 = new OxyPlot.Axes.LinearAxis();
                OxyPlot.Axes.LinearAxis linearAxis2 = new OxyPlot.Axes.LinearAxis();

                linearAxis1.Position = OxyPlot.Axes.AxisPosition.Bottom;

                plot.Axes.Add(linearAxis1);
                plot.Axes.Add(linearAxis2);

                OxyPlot.Series.LineSeries lineSeries1 = new OxyPlot.Series.LineSeries();
                lineSeries1.MarkerFill = OxyPlot.OxyColor.FromRgb(30, 144, 255);
                lineSeries1.MarkerSize = 1;
                lineSeries1.MarkerStrokeThickness = 1;
                lineSeries1.Title = "Accelerations (Accelerometer)";
                lineSeries1.XAxisKey = "seconds";
                lineSeries1.YAxisKey = "m/s^2";

                int x = 1;
                List<OxyPlot.DataPoint> datapoints = new List<OxyPlot.DataPoint>();
                foreach (ReportModel.SnapshotModel snapshot in model.Snapshots)
                {
                    lineSeries1.Points.Add(new OxyPlot.DataPoint(x, snapshot.Acceleration));
                    x++;
                }

                plot.Series.Add(lineSeries1);
                plot.Update();

                OxyPlot.Wpf.ShapesRenderContext rc = new OxyPlot.Wpf.ShapesRenderContext(null);
                string svg = plot.ToSvg(860, 520, false, rc);
                model.SVGPlotAcceleration = svg;
            }
                ));
            t.SetApartmentState(ApartmentState.STA);
            t.Start();

            Thread.Sleep(2000);
        }

        /// <summary>
        /// Generates SVG XML string for Speed & Limit vs. Time graph and saves it to model
        /// </summary>
        /// <param name="model"></param>
        private void GenerateSpeedPlot(ReportModel model)
        {
            Thread t = new Thread(new ThreadStart(() =>
            {
                OxyPlot.Series.LineSeries lineSeries2;

                OxyPlot.PlotModel plot = new OxyPlot.PlotModel();
                plot.LegendBackground = OxyPlot.OxyColor.FromRgb(255, 255, 255);
                plot.Title = "Driver Speed Graph";

                OxyPlot.Axes.LinearAxis linearAxis1 = new OxyPlot.Axes.LinearAxis();
                OxyPlot.Axes.LinearAxis linearAxis2 = new OxyPlot.Axes.LinearAxis();

                linearAxis1.Position = OxyPlot.Axes.AxisPosition.Bottom;

                plot.Axes.Add(linearAxis1);
                plot.Axes.Add(linearAxis2);

                OxyPlot.Series.LineSeries lineSeries1 = new OxyPlot.Series.LineSeries();
                lineSeries1.MarkerFill = OxyPlot.OxyColor.FromRgb(255, 127, 80);
                lineSeries1.MarkerSize = 1;
                lineSeries1.MarkerStrokeThickness = 1;
                lineSeries1.Title = "Speed";
                lineSeries1.XAxisKey = "seconds";
                lineSeries1.YAxisKey = "mph";

                lineSeries2 = new OxyPlot.Series.LineSeries();
                lineSeries2.Color = OxyPlot.OxyColor.FromRgb(255, 0, 0);
                lineSeries2.MarkerFill = OxyPlot.OxyColor.FromRgb(255, 0, 0);
                lineSeries2.MarkerSize = 1;
                lineSeries2.MarkerStrokeThickness = 1;
                lineSeries2.Title = "Limit";
                lineSeries2.XAxisKey = "seconds";
                lineSeries2.YAxisKey = "mph";

                int x = 1;
                foreach (ReportModel.SnapshotModel snapshot in model.Snapshots)
                {
                    lineSeries1.Points.Add(new OxyPlot.DataPoint(x, snapshot.Speed));
                    lineSeries2.Points.Add(new OxyPlot.DataPoint(x, snapshot.SpeedLimit));
                    x++;
                }

                plot.Series.Add(lineSeries1);
                plot.Series.Add(lineSeries2);
                plot.Update();

                OxyPlot.Wpf.ShapesRenderContext rc = new OxyPlot.Wpf.ShapesRenderContext(null);
                string svg = plot.ToSvg(860, 520, false, rc);
                model.SVGPlotSpeed = svg;
            }
                ));
            t.SetApartmentState(ApartmentState.STA);
            t.Start();

            Thread.Sleep(2000);
        }

        /// <summary>
        /// Generates JSON string from report
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        private void SaveToJSON(ReportModel model, string uniqueId)
        {
            List<object> jSnapshots = new List<object>();
            foreach (ReportModel.SnapshotModel snapshot in model.Snapshots)
            {
                List<Dictionary<string, object>> flags = new List<Dictionary<string, object>>();
                foreach (ReportModel.BehaviorClassifications flag in snapshot.Flags)
                {
                    Dictionary<string, object> jFlag = new Dictionary<string, object>();
                    jFlag["type"] = flag.ToString(); // Type of aggressive behavior
                    if (flag == ReportModel.BehaviorClassifications.ExcessiveAcceleration)
                    {
                        jFlag["duration"] = snapshot.ExcessiveAccelerationCounter;
                        jFlag["deductions"] = snapshot.ExcessiveAccelerationDeductions;
                    }
                    else if (flag == ReportModel.BehaviorClassifications.SuddenBraking)
                    {
                        jFlag["duration"] = snapshot.SuddenBrakingCounter;
                        jFlag["deductions"] = snapshot.SuddenBrakingDeductions;
                    }
                    else if (flag == ReportModel.BehaviorClassifications.HardTurn)
                    {
                        jFlag["duration"] = snapshot.HardTurnCounter;
                        jFlag["deductions"] = snapshot.HardTurnDeductions;
                    }
                    else if (flag == ReportModel.BehaviorClassifications.Speeding)
                    {
                        jFlag["duration"] = snapshot.SpeedingCounter;
                        jFlag["deductions"] = snapshot.SpeedingDeductions;
                    }
                    // jFlag["cost"] = (double)model.Deductions[flag]; // Amount of points to deduct
                    //jFlag[flag.ToString()] = (double)model.Deductions[flag];
                    flags.Add(jFlag);
                }

                object json = new
                {
                    Acceleration = snapshot.Acceleration,
                    Entry = snapshot.Entry,
                    Flags = flags,
                    Fuel = snapshot.Fuel,
                    Heading = snapshot.Heading,
                    Latitude = snapshot.Latitude,
                    Longitude = snapshot.Longitude,
                    Speed = snapshot.Speed,
                    SpeedLimit = snapshot.SpeedLimit,
                    Throttle = snapshot.Throttle,
                    XAccel = snapshot.XAxis,
                    YAccel = snapshot.YAxis,
                    ZAccel = snapshot.ZAxis
                }; // Create JSON snapshot object

                jSnapshots.Add(json);
            }

            // string jsonString = JsonConvert.SerializeObject(new { Grade = model.Grade, Points = model.Points, Summary = model.Summary, VIN = model.VIN, Snapshots = jSnapshots });// Create JSON report string
            string jsonString = JsonConvert.SerializeObject(new
            {
                Grade = model.Grade,
                Points = model.Points,
                VIN = model.VIN,
                ExcessiveAccelerationCount = model.ExcessiveAccelerationCount,
                ExcessiveAccelerationDeductions = model.ExcessiveAccelerationTotal,
                SuddenBrakingCount = model.SuddenBrakingCount,
                SuddenBrakingDeductions = model.SuddenBrakingTotal,
                HardTurningCount = model.HardTurnCount,
                HardTurningDeductions = model.HardTurnTotal,
                SpeedingCount = model.SpeedingCount,
                SpeedingDeductions = model.SpeedingTotal,
                Snapshots = jSnapshots
            });

            CloudBlockBlob jsonBlock = reportsContainer.GetBlockBlobReference(model.VIN + "_" + uniqueId);

            Trace.WriteLine("Saving data to blob...", "Information");

            using (Stream stream = GenerateStreamFromString(jsonString))
            {
                jsonBlock.UploadFromStream(stream);
            }

            Trace.WriteLine("Assigning metadata to blob...", "Information");

            try
            {
                jsonBlock.Metadata["grade"] = (model.Grade != null) ? model.Grade : "NaN";
                jsonBlock.Metadata["points"] = (model.Points != null) ? model.Points.ToString("F2") : "NaN";
                jsonBlock.Metadata["VIN"] = (model.VIN != null) ? model.VIN : "NaN";
                jsonBlock.Metadata["infractions"] = findInfractionCode(model);
                jsonBlock.SetMetadata();
            }
            catch (Microsoft.WindowsAzure.Storage.StorageException ex)
            {
                Trace.WriteLine("Failed to assign metadata. " + ex.Message, "Error");
                Trace.TraceError("An error occurred while attempting to assign metadata {Grade:{0},VIN:{1},Points:{2}}. {3}", model.Grade, model.VIN, model.Points.ToString("F2"), ex.ToString());
            }
            catch (SystemException ex)
            {
                Trace.WriteLine("Failed to assign metadata. " + ex.Message, "Error");
            }

            Trace.WriteLine("Save completed.", "Information");
        }

        private string findInfractionCode(ReportModel model)
        {
            // Array to keep track of which infractions have been found.  The ordering is Speeding (0), Excessive Acceleration (1), Sudden Braking (2), and High Speed Turning (3)
            int[] arr = { 0, 0, 0, 0 };
            int code = 0;

            if (model.SpeedingCount > 0)
            {
                arr[0] = 1;
            }
            else if (model.ExcessiveAccelerationCount > 0)
            {
                arr[1] = 2;
            }
            else if (model.SuddenBrakingCount > 0)
            {
                arr[2] = 4;
            }
            else if (model.HardTurnCount > 0)
            {
                arr[3] = 8;
            }

            code = arr[0] + arr[1] + arr[2] + arr[3];

            return "" + code;
        }

        private Stream GenerateStreamFromString(string s)
        {
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream);
            writer.Write(s);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }

        #endregion
    }
}
